/*
 * Copyright (c) 2006,2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if STDC_HEADERS
# include <stdlib.h>
# include <stdarg.h>
# include <string.h>
# include <float.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <assert.h>

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#if HAVE_PWD_H
# include <pwd.h>
#endif
#if HAVE_SYS_CYGWIN_H
# include <sys/cygwin.h>
#endif
#if HAVE_WINDOWS_H && HAVE_OPENCLIPBOARD
# define WIN32_LEAD_AND_MEAN
# define NOMINMAX
# include <windows.h>
#endif

#include <ustr.h>
#include "util.h"

char *program_name = NULL;

static int gui_mode = 1; /* enabled */
static int tty_mode = 1; /* enabled */
static int verbose_level = 0;  /* only FATAL errors, by default */

int run2_gui_is_allowed(void)  { return gui_mode;    }
int run2_tty_is_allowed(void)  { return tty_mode;    }
int run2_get_verbose_level(void) { return verbose_level; }

void run2_set_gui_mode(int mode)    { gui_mode = mode;    }
void run2_set_tty_mode(int mode)    { tty_mode = mode;    }
void run2_set_verbose_level(int level){ verbose_level = level;}

const char *
run2_get_program_name (void)
{
  if (!program_name || !*program_name)
    return "run2";
  return program_name;
}

void
run2_set_program_name (const char *p)
{
  if (program_name)
    free (program_name);
  program_name = run2_strdup (run2_basename (p));
}

const char *
run2_basename (const char *name)
{
  const char *base;

#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER)
  /* Skip over the disk name in MSDOS pathnames. */
  if (isalpha ((unsigned char) name[0]) && name[1] == ':')
    name += 2;
#endif

  for (base = name; *name; name++)
    if ((*name == '/')
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER)
       || (*name == '\\')
#endif
       )
      base = name + 1;
  return base;
}


/*
 * This function attempts to locate a file which may be in any of
 * several directories. Unlike the original pfopen, it does not
 * return a FILE pointer to the opened file, but rather returns
 * the fully-qualified filename of the first match found. Returns
 * empty string if not found.
 */
char *
run2_pfopen(char *retval, const char *name, const char *dirs)
{
    char *ptr;
    char *tdirs;
    char returnval[MAX_PATH + FILENAME_MAX + 100];
    char *recursive_name;
    int foundit = FALSE;

    returnval[0] = '\0';

    if (dirs == NULL || dirs[0] == '\0')
        return NULL;

    tdirs = (char *) run2_malloc(strlen(dirs)+1);
    strcpy(tdirs, dirs);

    for (ptr = strtok(tdirs, SEP_CHARS); (foundit == FALSE) && ptr != NULL;
         ptr = strtok(NULL, SEP_CHARS))
    {
       foundit = run2_fileExists(returnval,ptr,name);
    }

    free(tdirs);
    if (!foundit)
        retval[0] = '\0';
    else
        strcpy(retval,returnval);
    return retval;
}

int
run2_fileExists(char* fullname, const char* path, const char* name)
{
   int retval = FALSE;
   FILE* file;
   size_t len;
   char work[FILENAME_MAX];
   char work2[MAX_PATH + FILENAME_MAX + 100];
   size_t wlen = sizeof(work);
   if (path != NULL)
   {
      strncpy(work, path, wlen);
      work[sizeof(work)-1] = 0;
      len = strlen(work);
      wlen = sizeof(work) - len;
      if (len && work[len-1] != '/' && work[len-1] != '\\')
      {
         strncat(work, PATH_SEP_CHAR_STR, wlen);
         work[sizeof(work)-1] = 0;
         wlen = sizeof(work) - strlen(work);
      }
   }
   else
      work[0]='\0';

   strncat(work, name, wlen);
   work[sizeof(work)-1] = 0;
   wlen = sizeof(work) - strlen(work);
   cygwin_conv_to_posix_path(work, work2);

   debugMsg (3, "(%s) looking for...\t%s", __func__, work2);

   file = fopen(work2, "rb");
   if (file != NULL)
   {
      if (fullname != NULL)
         strcpy(fullname,work2);
      retval = TRUE;
      fclose(file);
   }
   return retval;
}


Ustr *
ustr_check (Ustr * s, const char * fn, int ln)
{
  if (!s)
  {
    run2_error (EXIT_FAILURE, ENOMEM,
                "Unable to allocate memory for ustr: %s:%d",
                fn, ln);
    abort ();
  }
  if ((ustr_ro (s) == USTR_FALSE) &&
      (ustr_enomem (s) == USTR_TRUE))
  {
    run2_error (EXIT_FAILURE, ENOMEM,
                "Unable to allocate memory for ustr: %s:%d",
                fn, ln);
    abort ();
  }
  return s;
}



void run2_message_(int level, const char* fmt, ...)
{
   va_list args;
   va_start(args, fmt);
   run2_vmessage_(level, fmt, args);
   va_end(args);
}

void run2_vmessage_(int level, const char* fmt, va_list args)
{
  char buf[10000];
  char titlebuf[200];
  int i,j;
  const char *pn = program_name;
  if (!pn || !*pn)
    pn = "run2";

  j = vsprintf(buf,fmt,args);
  if (ENABLE_TTY && (run2_tty_is_allowed()))
    {
      if (level >= RUN2_LOG_DEBUG)
        fprintf(stderr, "%s DEBUG: %s\n", pn, buf);
      else if (level < RUN2_LOG_FATAL)
        fprintf (stdout, buf);
      else
        switch (level)
          {
            case RUN2_LOG_FATAL: fprintf(stderr, "%s FATAL: %s\n", pn, buf); break;
            case RUN2_LOG_ERROR: fprintf(stderr, "%s Error: %s\n", pn, buf); break;
            case RUN2_LOG_WARN:  fprintf(stderr, "%s Warning: %s\n", pn, buf); break;
            case RUN2_LOG_INFO:  fprintf(stderr, "%s Info: %s\n", pn, buf); break;
            default: fprintf(stderr, "%s <unknown criticality>: %s\n", pn, buf); break;
          }
      return;
    }
  if (ENABLE_GUI && (run2_gui_is_allowed()))
    {
      i = 0;
      if (level >= RUN2_LOG_DEBUG)
        i = sprintf(titlebuf, "%s DEBUG", pn);
      else if (level < RUN2_LOG_FATAL)
        i = sprintf(titlebuf, "%s", pn);
      else
        switch (level)
          {
            case RUN2_LOG_FATAL: i = sprintf(titlebuf, "%s FATAL", pn); break;
            case RUN2_LOG_ERROR: i = sprintf(titlebuf, "%s Error", pn); break;
            case RUN2_LOG_WARN:  i = sprintf(titlebuf, "%s Warning", pn); break;
            case RUN2_LOG_INFO:  i = sprintf(titlebuf, "%s Info", pn); break;
            default: i = sprintf(titlebuf, "%s <unknown criticality>", pn); break;
          }

      if (level >= RUN2_LOG_DEBUG)
        MessageBox(NULL, buf, titlebuf, MB_ICONINFORMATION);
      else if (level < RUN2_LOG_FATAL)
        MessageBox(NULL, buf, titlebuf, MB_ICONINFORMATION);
      else
        switch (level)
          {
            case RUN2_LOG_FATAL: MessageBox(NULL, buf, titlebuf, MB_ICONERROR); break;
            case RUN2_LOG_ERROR: MessageBox(NULL, buf, titlebuf, MB_ICONERROR); break;
            case RUN2_LOG_WARN:  MessageBox(NULL, buf, titlebuf, MB_ICONWARNING); break;
            case RUN2_LOG_INFO:  MessageBox(NULL, buf, titlebuf, MB_ICONINFORMATION); break;
            default: MessageBox(NULL, buf, titlebuf, MB_ICONERROR); break;
          }
      return;
    }
}

int
run2_countargv (char **vector)
{
  char **scan;
  int rc = 0;
  if (vector != NULL)
    for (scan = vector; *scan != NULL; scan++)
      rc++;
  return rc;
}

void
run2_freeargv (char **vector)
{
  char **scan;
  if (vector != NULL)
    {
      for (scan = vector; *scan != NULL; scan++)
        {
          free (*scan);
          *scan = NULL;
        }
      free (vector);
    }
}

static void
run2_verror (int status, int errnum, const char *message, va_list args)
{
  if (errnum)
  {
    /* grow the message to hold error info */
    char const *s = strerror (errnum);
    char *newmsg;
    if (!s) s = "Unknown system error";
    newmsg = run2_extend_str (message, ": ", 1);
    newmsg = run2_extend_str (newmsg, s, 1);
    verrorMsg (newmsg, args);
  }
  else
  {
    verrorMsg (message, args);
  }
  if (status)
    exit (status);
}

void
run2_error (int status, int errnum, const char *message, ...)
{
  va_list args;
  va_start (args, message);
  run2_verror (status, errnum, message, args);
  va_end (args);
}

void
run2_malloc_exit (void)
{
  run2_error (EXIT_FAILURE, 0, "Unable to allocate memory");
  abort ();
}

void*
run2_malloc (size_t sz)
{
  void *p = malloc (sz);
  if (!p && sz != 0)
    run2_malloc_exit ();
  return p;
}

void*
run2_realloc (void *p, size_t sz)
{
  p = realloc (p, sz);
  if (!p && sz != 0)
    run2_malloc_exit ();
  return p;
}

char*
run2_strdup (const char *s)
{
  size_t len = strlen (s);
  char * d = (char *) run2_malloc (len + 1);

  if (s)
    strncpy (d, s, len + 1);
  else
    d[0] = '\0';

  return d;
}

char *
run2_extend_str (const char *orig_value, const char *add, int to_end)
{
  char *new_value;
  if (orig_value && *orig_value)
    {
      int orig_value_len = strlen (orig_value);
      int add_len = strlen (add);
      new_value = (char *) run2_malloc (add_len + orig_value_len + 1);
      if (to_end)
        {
          strcpy (new_value, orig_value);
          strcpy (new_value + orig_value_len, add);
        }
      else
        {
          strcpy (new_value, add);
          strcpy (new_value + add_len, orig_value);
        }
    }
  else
    {
      new_value = run2_strdup (add);
    }
  return new_value;
}

int
run2_strtol(char *arg, long *value)
{
  char *endptr;
  int errno_save = errno;

  assert(arg!=NULL);
  errno = 0;
  *value = strtol(arg, &endptr, 0);
  if (errno != 0 || *endptr!='\0' || endptr==arg) {
    return 1;
  }
  errno = errno_save;
  return 0;
}

#if HAVE_PWD_H
char *
run2_get_homedir (const char *user)
{
  char *home;
  struct passwd *pwd;

  if (!user || !*user)
    {
      home = getenv("HOME");
      if (home && *home)
        return run2_strdup (home);
      errno = 0;
      pwd = getpwuid (getuid());
      if (pwd)
        return run2_strdup (pwd->pw_dir);
      errorMsg ("Could not determine home directory for current user");
      return NULL;
    }

  errno = 0;
  pwd = getpwnam (user);
  if (pwd)
    return run2_strdup (pwd->pw_dir);
  errorMsg ("Could not determine home directory for user %s", user);
  return NULL;
}
#else
char *
run2_get_homedir (const char *user)
{
  char *home;

  if (!user || !*user)
    {
      home = getenv("HOME");
      if (home && *home)
        return run2_strdup (home);
      errorMsg ("Could not determine home directory for current user");
      return NULL;
    }

  errorMsg ("Could not determine home directory for user %s", user);
  return NULL;
}
#endif

